﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Sanford.Multimedia.Midi;

namespace MidiMix
{
    public partial class MidiFileInspector : Form
    {
        public MidiFileInspector()
        {
            InitializeComponent();
            listViewTracks.Enabled = listViewNotes.Enabled = false;
        }
        private Sequence sequence=null;
        public Sequence Sequence
        {
            set
            {
                sequence = value;
                populateTrackList();
            }
        }
        private void buttonClose_Click(object sender, EventArgs e)
        {
            this.Close();
        }
        private void populateTrackList()
        {
            listViewTracks.Items.Clear();
            listViewNotes.Items.Clear();
            if (sequence == null) return;
            for (int i = 0; i < sequence.Count; i++)
            {
                Track t = sequence[i];
                string[] items = new string[4];
                items[0] = Convert.ToString(i+1); // track num - throughout UI we are 1-based
                items[1] = t.TrackName;
                items[2] = Convert.ToString(t.NumNotes);
                items[3] = Convert.ToString(t.NumNonNotes);
                ListViewItem item = new ListViewItem(items);
                item.Tag = t;
                listViewTracks.Items.Add(item);
            }
            listViewTracks.Enabled = true;

            // As long as there is at least one track, select it - this will force the population
            // of the event list
            if (listViewTracks.Items.Count > 0)
            {
                listViewTracks.Items[0].Selected = true;
            }
        }
        private void populateEventList()
        {
            listViewNotes.Enabled =listViewTempo.Enabled = listViewOther.Enabled = false;
            if (listViewTracks.SelectedItems.Count == 0) return;
            
            Track t = (Track)(listViewTracks.SelectedItems[0].Tag);
            
            listViewNotes.Items.Clear();
            listViewTempo.Items.Clear();
            listViewOther.Items.Clear();
            textLyrics.Clear();

            for (int i = 0; i < t.Count; i++)
            {
                MidiEvent me = t.GetMidiEvent(i);
                addMessage(me);
            }
            listViewNotes.Enabled = listViewTempo.Enabled = listViewOther.Enabled = true;
        }
        private void addMessage(MidiEvent me)
        {
            bool noteOn, noteOff, addToTempo, addToOther, addToLyrics ;
            noteOn=noteOff =addToTempo = addToOther=addToLyrics =false;
            // beats will be 1 based.
            int beats = 1 + (int)Math.Floor((double)me.AbsoluteTicks / sequence.Division);
            int pulses = me.AbsoluteTicks % sequence.Division;
            string eventTime = Convert.ToString(beats + ": " + pulses);

            switch (me.MidiMessage.MessageType)
            {
                case MessageType.Channel:
                    ChannelMessage cm = (ChannelMessage) me.MidiMessage;
                    switch (cm.Command) {
                        case ChannelCommand.NoteOn:
                            if (cm.Data2>0)
                                noteOn=true;
                            else // NoteOff is often sent as a NoteOn event with 0 velocity.
                                noteOff=true;
                            break;
                        case ChannelCommand.NoteOff:
                            noteOff=true;
                            break;
                        default:
                            addToOther = true;
                            break;
                    }
                    break;
                case MessageType.Meta:
                    MetaMessage mm = (MetaMessage)me.MidiMessage;
                    if (mm.MetaType == MetaType.Tempo)
                        addToTempo = true;
                    else if (mm.MetaType == MetaType.Lyric)
                        addToLyrics = true;
                    else
                        addToOther = true;
                    break;
                   
                default:
                    addToOther = true;
                    break;
            }
            if (noteOn) {
                ChannelMessage cm = (ChannelMessage)me.MidiMessage;
                string[] items = new string[6];
                items[0] = eventTime;
                items[1] = Convert.ToString(cm.MidiChannel); 
                items[2] = noteToString(cm.Data1); // note
                items[3] = Convert.ToString(cm.Data2); // velocity
                items[4] = ""; // this will be filled out when the noteOff is processed
                ListViewItem item = new ListViewItem(items);
                item.Tag = me;
                listViewNotes.Items.Add(item);
            }
            else if (noteOff)
            {
                ChannelMessage cm = (ChannelMessage)me.MidiMessage;
                // update the duration for the previous noteOn event for this note
                for (int i = listViewNotes.Items.Count - 1; i >= 0; i--)
                {
                    ListViewItem item = listViewNotes.Items[i];
                    MidiEvent itemMidiEvent = (MidiEvent)item.Tag;
                    if (((ChannelMessage) itemMidiEvent.MidiMessage).Data1 == cm.Data1) // note matches
                    {
                        // beats will be 1 based.
                        int length = me.AbsoluteTicks - itemMidiEvent.AbsoluteTicks;

                        int lengthBeats = (int)Math.Floor((double)length/ sequence.Division);
                        int lengthPulses = length % sequence.Division;
                        string lengthString = Convert.ToString(lengthBeats + ": " + lengthPulses);

                        item.SubItems.Insert(4, new ListViewItem.ListViewSubItem(item,lengthString));
                        break;
                    }
                }
            }
            else if (addToTempo)
            {
                MetaMessage mm = (MetaMessage)me.MidiMessage;
                TempoChangeBuilder builder = new TempoChangeBuilder(mm);
                const int MICROSECONDS_PER_MINUTE = 60000000;
                int bpm = MICROSECONDS_PER_MINUTE / builder.Tempo;
                string[] items = new string[2];
                items[0] = eventTime;
                items[1] = Convert.ToString(bpm);
                ListViewItem item = new ListViewItem(items);
                item.Tag = me;
                listViewTempo.Items.Add(item);
            }
            else if (addToLyrics)
            {
                MetaMessage mm = (MetaMessage)me.MidiMessage;
                textLyrics.Text += Convert.ToString(System.Text.ASCIIEncoding.ASCII.GetString(mm.GetBytes()));

            }
            else if (addToOther)
            {
                string[] items = new string[2];
                items[0] = eventTime;
                items[1] = Convert.ToString(me.MidiMessage.MessageType);
                ListViewItem item = new ListViewItem(items);
                item.Tag = me;
                listViewOther.Items.Add(item);
            }

        }

        private string noteToString(int initialNote) {
            String[] noteString = new String[] { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };

            int octave = (initialNote / 12) - 1;
            int noteIndex = (initialNote % 12);
            return noteString[noteIndex] + octave;
        }

        private string getMidiEventType(MidiEvent me)
        {
            string eventType ="";
            switch (me.MidiMessage.MessageType)
            {
                case MessageType.Channel:
                    ChannelMessage cm = (ChannelMessage)me.MidiMessage;
                    switch(cm.Command) {
                        case ChannelCommand.ChannelPressure:
                            eventType = "Channel Pressure";
                            break;
                        case ChannelCommand.Controller:
                            eventType = "Controller";
                            break;
                        case ChannelCommand.NoteOff:
                            eventType = "Note Off";
                            break;
                        case ChannelCommand.NoteOn:
                            eventType = "Note On";
                            break;
                        case ChannelCommand.PitchWheel:
                            eventType = "Pitch Wheel";
                            break;
                        case ChannelCommand.PolyPressure:
                            eventType = "Poly Pressure";
                            break;
                        case ChannelCommand.ProgramChange:
                            eventType = "Program Change";
                            break;
                        default:
                            eventType = "Unknown Channel Message";
                            break;

                    }
                    break;
                case MessageType.SystemExclusive:
                    eventType = "System Exclusive";
                    break;
                case MessageType.SystemCommon:
                    eventType = "System Common";
                    break;
                case MessageType.SystemRealtime:
                    eventType = "System Realtime";
                    break;
                case MessageType.Meta:
                    MetaMessage mm = (MetaMessage)me.MidiMessage;
                    switch (mm.MetaType)
                    {
                        case MetaType.SequenceNumber:
                            eventType = "Sequence Number";
                            break;

                        case MetaType.Text:
                            eventType = "Text";
                            break;

                        case MetaType.Copyright:
                            eventType = "Copyright";
                            break;

                        case MetaType.TrackName:
                            eventType = "TrackName";
                            break;

                        case MetaType.InstrumentName:
                            eventType = "Instrument Name";
                            break;

                        case MetaType.Lyric:
                            eventType = "Lyric";
                            break;

                        case MetaType.Marker:
                            eventType = "Marker";
                            break;

                        case MetaType.CuePoint:
                            eventType = "Cue Point";
                            break;

                        case MetaType.ProgramName:
                            eventType = "Program Name";
                            break;

                        case MetaType.DeviceName:
                            eventType = "Device Name";
                            break;

                        case MetaType.EndOfTrack:
                            eventType = "End of Track";
                            break;

                        case MetaType.Tempo:
                            eventType = "Tempo";
                            break;

                        case MetaType.SmpteOffset:
                            eventType = "SMPTE Offset";
                            break;

                        case MetaType.TimeSignature:
                            eventType = "Time Signature";
                            break;

                        case MetaType.KeySignature:
                            eventType = "Key Signature";
                            break;

                        case MetaType.ProprietaryEvent:
                            eventType = "Proprietary Event";
                            break;
                        default:
                            eventType = "Unknown Meta Event";
                            break;
                    }

                    break;
                default:
                    eventType = "Unknown";
                    break;
            }
            return eventType;
        }
        private void listView1_SelectedIndexChanged(object sender, EventArgs e)
        {
            populateEventList();
        }

        private void listView2_SelectedIndexChanged(object sender, EventArgs e)
        {
            
        }
    }
}
